{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 基础知识"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. PyTorch基础"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.1 Tensor(张量)\n",
    "多维矩阵，可以看作可以在GPU上运行的numpy数组"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'0.4.1'"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import torch\n",
    "import numpy as np\n",
    "torch.__version__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "|表达形式|数据类型|\n",
    "|-|-|\n",
    "|torch.tensor|根据数据类型变更|\n",
    "|torch.Tensor|32位浮点型|\n",
    "|torch.FloatTensor|32位浮点型|\n",
    "|torch.DoubleTensor|64位浮点型|\n",
    "|torch.shortTensor|16位整型|\n",
    "|torch.IntTensor|32位整型|\n",
    "|torch.LongTensor|64位整型|"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.创建零维向量: tensor(4.4000) torch.Size([]) torch.FloatTensor\n",
      "2.创建向量: tensor([[1, 2],\n",
      "        [3, 4]]) torch.Size([2, 2])\n"
     ]
    }
   ],
   "source": [
    "a = torch.tensor(4.4)                           # 参数是tensor中的数据\n",
    "print('1.创建零维向量:', a, a.shape, a.type())\n",
    "b = torch.tensor([[1,2], [3,4]])                # 用法类似于numpy.array()\n",
    "print('2.创建向量:', b, b.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.Tensor a is:\n",
      " tensor([[2., 3.],\n",
      "        [4., 8.],\n",
      "        [7., 9.]])\n",
      "2.a shape is: torch.Size([3, 2]) torch.Size([3, 2])\n",
      "3.a type is: <class 'torch.Tensor'>\n",
      "4.Tensor b is:\n",
      " tensor([[[0.0000, 0.0000, 0.0000],\n",
      "         [0.0000, 0.0000, 0.0000]]])\n",
      "5.Tensor b's shape: torch.Size([1, 2, 3])\n"
     ]
    }
   ],
   "source": [
    "# 创建torch向量\n",
    "a = torch.Tensor([[2, 3], [4, 8], [7, 9]])   # 默认的是torch.FloatTensor\n",
    "print('1.Tensor a is:\\n', a)\n",
    "print('2.a shape is:', a.size(), a.shape)    # 可以使用两个方法输出size\n",
    "print('3.a type is:', type(a))\n",
    "b = torch.Tensor(1,2,3)                      # 可以定义特定维度的向量\n",
    "print('4.Tensor b is:\\n', b)\n",
    "print('5.Tensor b\\'s shape:', b.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.Tensor b is:\n",
      " tensor([[2, 3],\n",
      "        [4, 8],\n",
      "        [7, 9]])\n",
      "3.a type is: <class 'torch.Tensor'>\n"
     ]
    }
   ],
   "source": [
    "b = torch.LongTensor([[2, 3], [4, 8], [7, 9]])\n",
    "print('1.Tensor b is:\\n', b)\n",
    "print('3.a type is:', type(b))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.Tensor c is:\n",
      " tensor([[0., 0.],\n",
      "        [0., 0.],\n",
      "        [0., 0.]])\n"
     ]
    }
   ],
   "source": [
    "# 创建零向量\n",
    "c = torch.zeros([3,2])   # 3*2的零向量\n",
    "print('1.Tensor c is:\\n', c)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.Tensor d is:\n",
      " tensor([[-0.2593,  0.1230],\n",
      "        [ 0.2828, -1.1419],\n",
      "        [ 0.2410, -1.3066]])\n"
     ]
    }
   ],
   "source": [
    "# 创建符合高斯分布的随机数据\n",
    "d = torch.randn([3,2])\n",
    "print('1.Tensor d is:\\n', d)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.Tensor a is:\n",
      " tensor([[2., 3.],\n",
      "        [4., 8.],\n",
      "        [7., 9.]])\n",
      "2.Tensor new a is:\n",
      " tensor([[ 2., 77.],\n",
      "        [ 4.,  8.],\n",
      "        [ 7.,  9.]])\n"
     ]
    }
   ],
   "source": [
    "a = torch.Tensor([[2, 3], [4, 8], [7, 9]]) \n",
    "print('1.Tensor a is:\\n', a)\n",
    "a[0, 1] = 77      # a[0][1] = 77，两种方式都可以进行数组元素的改写\n",
    "print('2.Tensor new a is:\\n', a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.convert Tensor to <class 'numpy.ndarray'> is:\n",
      "[[2. 3.]\n",
      " [4. 8.]\n",
      " [7. 9.]]\n",
      "2.from numpy to <class 'torch.Tensor'> is:\n",
      "tensor([[1, 2, 3],\n",
      "        [4, 5, 6]], dtype=torch.int32)\n",
      "3.<class 'torch.Tensor'> is:\n",
      "tensor([[1., 2., 3.],\n",
      "        [4., 5., 6.]])\n"
     ]
    }
   ],
   "source": [
    "# torch.Tensor和numpy.array之间互相转换\n",
    "a = torch.Tensor([[2, 3], [4, 8], [7, 9]]) \n",
    "# print('0.{} a is:\\n{}'.format(type(a), a))\n",
    "numpy_a = a.numpy()            # 转换为numpy的数组\n",
    "print('1.convert Tensor to {} is:\\n{}'.format(type(numpy_a), numpy_a))\n",
    "numpy_e = np.array([[1,2,3],[4,5,6]])\n",
    "# print('2.{} e is:\\n{}'.format(type(numpy_e), numpy_e))\n",
    "e = torch.from_numpy(numpy_e)   # 转换为pytorch的Tensor\n",
    "print('2.from numpy to {} is:\\n{}'.format(type(e), e))\n",
    "f_torch_e = e.float()           # pytorch的Tensor类型转换为float\n",
    "print('3.{} is:\\n{}'.format(type(f_torch_e), f_torch_e))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[2., 3.],\n",
      "        [4., 8.],\n",
      "        [7., 9.]], device='cuda:0')\n"
     ]
    }
   ],
   "source": [
    "# pytorch支持GPU\n",
    "a = torch.Tensor([[2, 3], [4, 8], [7, 9]])\n",
    "if torch.cuda.is_available():\n",
    "    a_cuda = a.cuda()       # 之前版本的方法\n",
    "    print(a_cuda)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.cpu上的变量: tensor([1, 2])\n",
      "2.GPU上的变量: tensor([1, 2], device='cuda:0')\n"
     ]
    }
   ],
   "source": [
    "# pytorch支持GPU\n",
    "use_cuda = torch.cuda.is_available()\n",
    "device = torch.device(\"cuda\" if use_cuda else \"cpu\")\n",
    "cpu_var = torch.tensor([1,2])\n",
    "print('1.cpu上的变量:', cpu_var)\n",
    "cuda_var = cpu_var.to(device)       # 新版本方法\n",
    "print('2.GPU上的变量:', cuda_var)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "小结:\n",
    "- `torch变量.numpy()` pytorch的tensor转换为numpy的array\n",
    "- `torch.from_numpy(numpy变量)` numpy的array转换为pytorch的tensor\n",
    "- `torch变量.X类型()` pytorch的tensor转换为相应的X类型"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.2 Variable(变量)\n",
    "Variable与Tensor类似，但Variable会放到计算图中，然后进行前向传播、后向传播和自动求导\n",
    "**注意：0.4版本开始Variable与tensor合并**，语法有些变化\n",
    "\n",
    "|属性|作用|\n",
    "|-|-|\n",
    "|data|Variable里的tensor数值|\n",
    "|grad|Variable的反向传播梯度|\n",
    "|grad_fn|得到Variable的操作|"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.输出梯度\n",
      "x的梯度: tensor([3.])\n",
      "w的梯度: tensor([2.])\n",
      "b的梯度: tensor([1.])\n"
     ]
    }
   ],
   "source": [
    "# 1. 对标量求导\n",
    "#-------------------------方法一-----------------------\n",
    "from torch.autograd import Variable\n",
    "# 创建变量，求导需要传入requires_grad参数，默认为False\n",
    "x = Variable(torch.Tensor([2]), requires_grad=True)   # Tensor变为Variable\n",
    "w = Variable(torch.Tensor([3]), requires_grad=True)\n",
    "b = Variable(torch.Tensor([4]), requires_grad=True)\n",
    "\n",
    "# 构建计算图\n",
    "y = w * x + b\n",
    "\n",
    "# 计算梯度\n",
    "y.backward()   # 对所有需要梯度的变量求导，标量求导不需要参数\n",
    "\n",
    "# 输出梯度\n",
    "print('1.输出梯度')\n",
    "print('x的梯度:', x.grad)\n",
    "print('w的梯度:', w.grad)\n",
    "print('b的梯度:', b.grad)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.输出梯度\n",
      "x的梯度: tensor([3.])\n",
      "w的梯度: tensor([2.])\n",
      "b的梯度: tensor([1.])\n"
     ]
    }
   ],
   "source": [
    "# 1. 对标量求导\n",
    "#-------------------------方法二-----------------------\n",
    "\n",
    "# 创建变量\n",
    "x = torch.Tensor([2]) \n",
    "w = torch.Tensor([3])\n",
    "b = torch.Tensor([4])\n",
    "\n",
    "x.requires_grad=True       # 使tensor需要计算梯度\n",
    "w.requires_grad=True\n",
    "b.requires_grad=True\n",
    "\n",
    "# 构建计算图\n",
    "y = w * x + b\n",
    "\n",
    "# 计算梯度\n",
    "y.backward()               # 对所有需要梯度的变量求导，标量求导不需要参数\n",
    "\n",
    "# 输出梯度\n",
    "print('1.输出梯度')\n",
    "print('x的梯度:', x.grad)\n",
    "print('w的梯度:', w.grad)\n",
    "print('b的梯度:', b.grad)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.向量y: tensor([-1.0518, -0.6878,  0.6593], grad_fn=<MulBackward>)\n",
      "2.x的梯度: tensor([2.0000, 0.2000, 0.0200])\n",
      "3.x的梯度:\n",
      " tensor([[2.0000, 0.2000],\n",
      "        [0.0200, 0.0020]])\n"
     ]
    }
   ],
   "source": [
    "# 2. 对矩阵求导\n",
    "x = torch.randn([3])\n",
    "x = Variable(x, requires_grad=True)    \n",
    "y = x * 2\n",
    "print('1.向量y:', y)\n",
    "y.backward(torch.FloatTensor([1, 0.1, 0.01]))  # 1*梯度 0.1*梯度 0.01*梯度\n",
    "print('2.x的梯度:', x.grad)     # x.grad的维度由[1, 0.1, 0.01]的维度决定\n",
    "x = torch.randn([2,2])\n",
    "x = Variable(x, requires_grad=True)\n",
    "y = x * 2\n",
    "y.backward(torch.Tensor([[1, 0.1], [0.01, 0.001]]))\n",
    "print('3.x的梯度:\\n', x.grad)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "小结:\n",
    "- `y = x * 2`中x梯度的维度由`y.backward(Tensor))`中Tensor的维度决定"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.3 Dataset(数据集)\n",
    "进行数据的读取和预处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "#继承和重写该类后可以定义自己的数据读取类\n",
    "from torch.utils.data import Dataset\n",
    "import pandas as pd\n",
    "class myDataset(Dataset):     # 继承于Dataset类\n",
    "    def __init__(self, csv_file, txt_file, root_dir, other_file):\n",
    "        self.csv_data = pd.read_csv(csv_file)           # 读取csv文件\n",
    "        with open(txt_file, 'r') as f:\n",
    "            self.txt_data = f.readlines()                # 读取txt文件\n",
    "        self.root_dir = root_dir\n",
    "    # 自定义的类需要定义__len__和__getitem__\n",
    "    def __len__(self):          \n",
    "        return len(self.csv_data)\n",
    "    def __getitem__(self, idx):\n",
    "        data = (self.csv_data[idx], self.txt_data[idx])  # 数据和标签\n",
    "        return data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "数据读取常用**迭代器**来实现batch数据读取、shuffle和多线程操作"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from torch.utils.data import DataLoader\n",
    "# collate_fn表示如何读取样本\n",
    "dataiter = DataLoader(myDataset, batch_size=32, shuffle=True, collate_fn=None)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "torchvision中的ImageFolder类常用来处理图像，数据存放形式如下:\n",
    "\n",
    "root/dog/xxx.png\n",
    "\n",
    "root/cat/xxx.png"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from torchvision.datasets import ImageFolder\n",
    "# transfor是图像增强，loader是图像读取的方法\n",
    "dset = ImageFolder(root='root_path', transform=None, loader=None)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.4 nn.Module(模组)\n",
    "torch.nn包含所有的层结构和损失函数，网络结构定义完成后相当于建立了计算图，每次使用该结构，都是在进行相同的前向传播，PyTorch可以自动求导"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 网络结构模板\n",
    "import torch.nn as nn\n",
    "# 定义网络结构\n",
    "class net_name(nn.Module):\n",
    "    def __init__(self, other_arguments):\n",
    "        super().__init__()      # super(net_name, self).__init__()也可以\n",
    "        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size)\n",
    "        # other network layer\n",
    "    def forward(self, x):\n",
    "        x = self.conv1(x)\n",
    "        return x\n",
    "# 损失函数\n",
    "criterion = nn.CrossEntropyLoss()     # 返回的是一个函数\n",
    "loss = criterion(output, target)      # 传入参数并调用该函数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.5 torch.optim(优化)\n",
    "通过迭代使损失函数逐渐变小，传入的参数必须是Variable"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义优化器\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)\n",
    "# 优化方法\n",
    "optimizer.zeros()    # 将梯度归零\n",
    "loss.backward()      # 误差方向传播\n",
    "optimizer.step()     # 通过梯度进一步更新参数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.6 模型的保存和加载\n",
    "1. 保存模型的结构和参数信息\n",
    "2. 保存模型的参数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "torch.save(model, './model.pth')                     # 保存模型结构和参数\n",
    "torch.save(model.state_dict(), './model_state.pth')  # 仅保存模型参数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "load_model = torch.load('model.pth')   # 加载模型的结构和参数\n",
    "# 如果仅导入模型参数，需要结构已知\n",
    "load_model = mymodel.load_state_dict(torch.load('./model_state.pth')) "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
